<?php
/**
 * 数据库操作
 * @author moufer<moufer@163.com>
 * @copyright (C)2001-2007 Moufersoft
 */
!defined('IN_MUDDER') && exit('Access Denied');

class ms_mysql
{

    public $dns = array();
    /**
     * @var mysqli
     */
    public $link;
    public $query_num = 0;
    public $catch     = FALSE;
    public $sqls      = '';

    function __construct(&$dns)
    {
        $this->dns = $dns;
        if (DEBUG) $this->sqls = array();
    }

    function connect()
    {
        if ($this->link && $this->link->ping()) {
            return;
        }
        if (DEBUG) {
            $mtime = explode(' ', microtime());
            $starttime = $mtime[1] + $mtime[0];
            $this->sqls[] = 'Connect MySQL';
        }
        $this->link = new mysqli($this->dns['dbhost'], $this->dns['dbuser'], $this->dns['dbpw']);
        if ($this->link->connect_error) {
            $this->_halt("Can not connect to MySQL server({$this->link->connect_errno})");
        }
        if (version_compare($this->version(), '5.0.1', '>=')) {
            $this->link->query("SET sql_mode=''");
        }
        $this->link->set_charset($this->dns['dbcharset']);
        if ($this->dns['dbname']) {
            if (!$this->link->select_db($this->dns['dbname'])) {
                $this->_halt('Cannot use database ' . $this->dns['dbname']);
            }
        }
        if (DEBUG) {
            $mtime = explode(' ', microtime());
            $querytime = number_format(($mtime[1] + $mtime[0] - $starttime), 6);
            $this->sqls[] = 'Time:' . $querytime;
        }
    }

    /**
     * 选择一个数据库
     *
     * @param string $dbname 数据库名
     */
    function select_db($dbname)
    {
        $this->dns['dbname'] = $dbname;
        if (!$this->link->select_db($this->dns['dbname'])) {
            $this->_halt('Cannot use database ' . $this->dns['dbname']);
        }
    }

    /**
     * 查询数据库版本信息
     *
     * @return string
     */
    function version()
    {
        return mysqli_get_server_info($this->link);
    }

    /**
     * Ping数据库，如果无法ping通，就重连数据库
     */
    function ping()
    {
        if ($this->link && !$this->link->ping()) {
            $this->close(); //注意：一定要先执行数据库关闭，这是关键
            $this->connect();
        }
    }

    /**
     * 发送一条 MySQL 查询
     *
     * @param string $SQL SQL语法
     * @param string $method 查询方式 [空=自动获取并缓存结果集] [unbuffer=并不获取和缓存结果的行]
     * @return ms_mysql_result|bool 资源标识符
     */
    function query($SQL, $method = '')
    {
        if (DEBUG) {
            $mtime = explode(' ', microtime());
            $starttime = $mtime[1] + $mtime[0];
            $this->sqls[] = 'Query' . ($this->query_num + 1) . ':' . $SQL;
        }
        //$this->safecheck($SQL);
        //if($this->dns['ping']) $this->ping();
        $query = mysqli_query($this->link, $SQL);

        if (!$query && $method != 'SILENT') {
            $this->_halt('MySQL Query Error: ' . $SQL);
            return false;
        }

        if (!is_object($query) || get_class($query) !== 'mysqli_result') return false;
        $this->query_num++;
        if (DEBUG) {
            $mtime = explode(' ', microtime());
            $querytime = number_format(($mtime[1] + $mtime[0] - $starttime), 6);
            $this->sqls[] = 'Time:' . $querytime;
            if (strtolower(substr($SQL, 0, 7)) == 'select ' && $this->link) {
                if ($result = mysqli_query($this->link, 'EXPLAIN ' . $SQL)) {
                    $explain = mysqli_fetch_assoc($result);
                    $table = '';
                    if ($explain) {
                        $table = '<table border="1" cellspacing="1" cellpadding="1"><tr>';
                        foreach (array_keys($explain) as $key) {
                            $table .= "<td>$key</td>";
                        }
                        $table .= '</tr><tr>';
                        foreach ($explain as $key => $val) {
                            $table .= "<td>$val</td>";
                        }
                        $table .= '</tr></table>';
                    }
                }
            }
            $this->sqls[] = $table;
        }
        if (mysqli_num_rows($query)) {
            $result = new ms_mysql_result($query);
        } else {
            $result = false;
        }
        return $result;
    }

    /**
     * 执行一条 MySQL 查询
     *
     * @param string $SQL SQL语法
     * @param string $method 查询方式 [空=自动获取并缓存结果集] [unbuffer=并不获取和缓存结果的行]
     * @return int
     */
    function exec($SQL, $method = '')
    {
        if (DEBUG) {
            $mtime = explode(' ', microtime());
            $starttime = $mtime[1] + $mtime[0];
            $this->sqls[] = 'Exec ' . ($this->query_num + 1) . ':' . $SQL;
        }
        $this->safecheck($SQL);
        if ($this->dns['ping']) $this->ping();

        $query = mysqli_query($this->link, $SQL);
        if (!$query && $method != 'SILENT') {
            $this->_halt('MySQL Query Error: ' . $SQL);
        }
        $arows = $this->affected_rows();
        $this->query_num++;
        if (DEBUG) {
            $mtime = explode(' ', microtime());
            $querytime = number_format(($mtime[1] + $mtime[0] - $starttime), 6);
            $this->sqls[] = 'Time:' . $querytime;
        }
        return $arows;
    }

    /**
     * 返回上一次执行SQL后，被影响修改的条(行)数
     *
     * @return int
     */
    function affected_rows()
    {
        return mysqli_affected_rows($this->link);
    }

    /**
     * 取得上一步 INSERT 操作产生的 ID
     *
     * @return int
     */
    function insert_id()
    {
        $id = mysqli_insert_id($this->link);
        return $id ?: 0;
    }

    /**
     * 关闭 MySQL 连
     *
     * @return bool
     */
    function close()
    {
        return mysqli_close($this->link);
    }

    /**
     * 返回上一个 MySQL 操作产生的文本错误信息
     *
     * @return string
     */
    function error()
    {
        return (($this->link) ? mysqli_error($this->link) : '');
    }

    /**
     * 返回上一个 MySQL 操作中的错误信息的数字编码
     *
     * @return integer
     */
    function errno()
    {
        return intval(($this->link) ? mysqli_errno($this->link) : 0);
    }

    // 获取一个表名
    function get_table($tablename)
    {
        if (preg_match("/^([`a-z0-9_]+)$/i", $tablename) || preg_match("/^([`a-z0-9_]+)\.([`a-z0-9_]+)$/i", $tablename)) {
            return str_replace("dbpre_", $this->dns['dbpre'], $tablename);
        }
        redirect('无效的数据表名称:' . _T($tablename));
    }

    function repace_table($tablename)
    {
        return str_replace("dbpre_", $this->dns['dbpre'], $tablename);
    }

    //转换插入sql的值
    function _escape($str)
    {
        switch (gettype($str)) {
            case 'boolean':
                $str = ($str === FALSE) ? 0 : 1;
                break;
            case 'integer':
                //$str = $str;
                break;
            default:
                $str = "'" . $this->_escape_str($str) . "'";
                break;
        }
        return $str;
    }

    /**
     * 对字符串处理加入引号
     *
     * @param string $str
     * @return string
     */
    function _escape_str($str)
    {
        if (function_exists('mysqli_real_escape_string')) {
            return mysqli_real_escape_string($this->link, $str);
        } else {
            return addslashes($str);
        }
    }

    /**
     * 过滤字段名
     *
     * @param string $field
     * @return string
     */
    function _ck_field($field)
    {
        if (preg_match("/[\'\\\"\<\>#]+/", $field)) {
            show_error(lang('global_sql_invalid_field', $field));
        }
        foreach (array('admin', 'members') as $key) {
            $table = $this->dns['dbpre'] . $key;
            if (strposex($field, $table)) show_error(lang('global_sql_invalid_field', $field));
        }
        return $field;
    }

    /**
     * 显示错误信息
     *
     * @param string $msg
     */
    function _halt($msg = '')
    {
        //如果设置错误捕获，则不跳出错误信息。
        if ($this->catch) {
            return;
        }
        global $_G;

        $message = "<html>\n<head>\n";
        $message .= "<meta content=\"text/html; charset=$_G[charset]\" http-equiv=\"Content-Type\">\n";
        $message .= "<link rel='stylesheet' type='text/css' href='" . URLROOT . "/static/images/error.css'>";
        $message .= "</head>\n<body>\n";

        $error = $this->error();
        if (strposex($msg, $this->dns['dbpre'] . 'admin')) {
            $message .= 'S' . 'Q' . 'L' . ' ' . 'E' . 'r' . 'r' . 'o' . 'r';
        } else {
            $msg = str_replace($this->dns['dbpre'], '[dbpre]', $msg);
            $msg = preg_replace("/[a-zA-Z0-9]{32}/i", '[md5str]', $msg);
            $msg = preg_replace("/[a-zA-Z0-9]{16}/", '[hash]', $msg);
            $error = str_replace($this->dns['dbpre'], '[dbpre]', $error);
            $error = preg_replace("/[a-zA-Z0-9]{32}/", '[md5str]', $error);
            $error = preg_replace("/[a-zA-Z0-9]{16}/", '[hash]', $error);

            $message .= "<div class='error'><h3>MySQL Error:</h3>";
            $message .= "<div class='error'>";
            $message .= "<p><code>" . _T($msg) . "</code>\n";
            $message .= "<b>Error description</b>: " . $error . "\n<br />";
            $message .= "<b>Error number</b>: " . $this->errno() . "\n<br />";
            $message .= "<b>Date</b>: " . date("Y-m-d @ H:i") . "\n<br />";
            $message .= "<b>Script</b>: http://" . $_SERVER['HTTP_HOST'] . getenv("REQUEST_URI") . "</p>\n";
            $message .= "</div>\n</div>";

            $trace = debug_backtrace();
            $message .= "<div class='debug'>\n<h3>Debug backtrace:</h3>";
            $message .= "<table border='0' cellspacing='0' cellpadding='0' class='trace'>";
            $message .= "<tr>\n<th width='400' align='left'>File</th>";
            $message .= "<th width=\"50\">Line</th>";
            $message .= "<th align=\"right\">Function</th>\n</tr>";
            foreach ($trace as $k => $t) {
                //if(!$k) continue;
                $message .= "<tr><td>" . str_replace(MUDDER_ROOT, '', $t['file']) . "</td>";
                $message .= "<td align='center'>$t[line]</td>";
                $message .= "<td align='right'>" . ($t['class'] ? ("{$t['class']}->") : '') . "$t[function]()</td></tr>";
            }
            $message .= "</table>\n</div>";
        }

        $message .= "</body>\n</html>";
        echo $message;

        log_write('sqlerror',
            $msg .
            "\nDescription:" . $error .
            "\nNumber:" . $this->errno() .
            "\nDate:" . date("Y-m-d @ H:i:s") .
            "\nScript:http://" . $_SERVER['HTTP_HOST'] . getenv("REQUEST_URI") .
            "\nIP:" . $_G['ip'] .
            "\n========================================================="
        );
        exit;
    }

    //SQL可以语句检测
    function safecheck($sql)
    {
        $result = $this->full_count_words($sql);
        if ($result['select'] >= 4 and $result['from'] >= 3) {
            log_write('sqlcheck',
                htmlspecialchars($sql) .
                "\nDate:" . date("Y-m-d @ H:i:s") .
                "\nScript:http://" . $_SERVER['HTTP_HOST'] . getenv("REQUEST_URI") .
                "\nIP:" . $this->global['ip'] .
                "\n========================================================="
            );
        }
    }

    function full_count_words($str)
    {
        $words = str_word_count($str, 1);
        $result = array();
        foreach ($words as $w) {
            $lw = strtolower($w);
            if (!(isset($result[$lw]))) {
                $result[$lw] = 1;
            } else {
                $result[$lw]++;
            }
        }
        return $result;
    }

    /**
     * 显示SQL查询记录
     *
     */
    function debug_print()
    {
        global $_G;
        if (!$this->sqls) return false;
        $style = 'margin:5px auto;width:98%;line-height:18px;font-family:Courier New;text-align:left;background:#eee;border-width:1px; border-style:solid;border-color:#CCC;';
        $content = '<div style="' . $style . '">';
        $content .= '<h3 style="font-size:16px;border-bottom:1px solid #FF9900;margin:5px;padding:0 0 5px;"><a href="javascript:;" onclick="$(\'#debug_sql_history\').toggle();">SQL History</a> (' . $this->query_num . ')</h3>';
        $content .= '<ul style="margin:0;padding:0 0 5px;list-style:none;display:none;" id="debug_sql_history">';
        foreach ($this->sqls as $val) {
            $content .= '<li style="padding:1px 8px;font-size:12px;">' . $val . '</li>';
        }
        $content .= '</ul></div>';

        return $content;
    }
}

class ms_mysql_result
{

    /**
     * @var mysqli_result
     */
    public $res;

    function __construct($res)
    {
        $this->res = $res;
    }

    function result($row)
    {
        $res = $this->fetch_array(MYSQLI_NUM);
        return isset($res[$row]) ? $res[$row] : null;
    }

    function fetch_array($result_type = MYSQLI_ASSOC)
    {
        //MYSQL_ASSOC，MYSQL_NUM 和 MYSQL_BOTH
        return mysqli_fetch_array($this->res, $result_type);
    }

    //获取数据，简化函数
    function fetch($result_type = MYSQLI_ASSOC)
    {
        //MYSQL_ASSOC，MYSQL_NUM 和 MYSQL_BOTH
        return mysqli_fetch_array($this->res, $result_type);
    }

    function fetch_row()
    {
        return mysqli_fetch_row($this->res);
    }

    function fetch_assoc()
    {
        return mysqli_fetch_assoc($this->res);
    }

    function fetch_field()
    {
        return mysqli_fetch_field($this->res);
    }

    function fetch_lengths()
    {
        return mysqli_fetch_lengths($this->res);
    }

    function fetch_object()
    {
        return mysqli_fetch_object($this->res);
    }

    function num_fields()
    {
        return mysqli_num_fields($this->res);
    }

    function num_rows()
    {
        return mysqli_num_rows($this->res);
    }

    function free_result()
    {
        mysqli_free_result($this->res);
    }

    //释放SQL资源，简化函数
    function free()
    {
        mysqli_free_result($this->res);
    }

}
